package com.ibm.iroo.addon.activiti;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.springframework.roo.classpath.operations.AbstractOperations;
import org.springframework.roo.model.JavaPackage;
import org.springframework.roo.process.manager.MutableFile;
import org.springframework.roo.project.Dependency;
import org.springframework.roo.project.FeatureNames;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.PathResolver;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.project.Property;
import org.springframework.roo.project.Repository;
import org.springframework.roo.support.util.FileUtils;
import org.springframework.roo.support.util.XmlElementBuilder;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Implementation of {@link ActivitiOperations} interface.
 *
 * @since 1.1.1
 */

@Component
@Service
public class ActivitiOperationsImpl extends AbstractOperations implements ActivitiOperations {

	//"com.ibm.zero-0.1.0.BUILD-SNAPSHOT.jar";

	private static final String IROO_CORE_PACKAGE = "com.ibm.iroo.activiti"; //com.ibm.zero

	private static final String IROO_CORE_VERSION = "1.0.0";

	private static final String IROO_CORE_LIBRARY = IROO_CORE_PACKAGE + "-" + IROO_CORE_VERSION + ".jar";
	/**
	 * Get hold of a JDK Logger
	 */
	private Logger log = Logger.getLogger(getClass().getName());

	@Reference
	private PathResolver pathResolver;

	private static final char SEPARATOR = File.separatorChar;

	/**
	 * Get a reference to the ProjectOperations from the underlying OSGi container. Make sure you
	 * are referencing the Roo bundle which contains this service in your add-on pom.xml.
	 */
	@Reference
	private ProjectOperations projectOperations;

	private static final Dependency SPRING_SECURITY = new Dependency("org.springframework.security",
			"spring-security-core", "3.1.0.RELEASE");

	/** {@inheritDoc} */
	public boolean isActivitiSetupCommandAvailable() {
		// Permit installation if they have a web project (as per ROO-342) and
		// no version of Spring Security is already installed.

		return projectOperations.isFocusedProjectAvailable() && isWebProject() && !isActivitiEngineInstalled()
				&& isSecurityInstalled() && !isJSFOrGWTFeatured();
	}

	public boolean isActivitiExplorerSetupCommandAvailable() {
		return projectOperations.isFocusedProjectAvailable() && isWebProject() && isActivitiEngineInstalled()
				&& isSecurityInstalled() && !isJSFOrGWTFeatured();// && !isActivitiExplorerInstalled();
	}

	private boolean isJSFOrGWTFeatured() {
		return projectOperations.isFeatureInstalledInFocusedModule(FeatureNames.JSF, FeatureNames.GWT);
	}

	private boolean isSecurityInstalled() {
		return projectOperations.getFocusedModule().hasDependencyExcludingVersion(SPRING_SECURITY);
	}

	private boolean isActivitiEngineInstalled() {
		return fileManager.exists(pathResolver.getFocusedIdentifier(Path.SPRING_CONFIG_ROOT,
				"applicationContext-activiti.xml"));
	}

	private boolean isWebProject() {
		return fileManager.exists(pathResolver.getFocusedIdentifier(Path.SRC_MAIN_WEBAPP, "WEB-INF/web.xml"));
	}

	private boolean isActivitiExplorerInstalled() {
		return fileManager.exists(pathResolver.getFocusedIdentifier(Path.SRC_MAIN_WEBAPP,
				"WEB-INF/i18n/activiti.properties"));
	}

	public boolean isActivitiSamplesSetupCommandAvailable() {
		return projectOperations.isFocusedProjectAvailable() && isWebProject() && isActivitiEngineInstalled()
				&& isSecurityInstalled() && !isJSFOrGWTFeatured();
	}

	/** {@inheritDoc} */
	public String getProperty(String propertyName) {
		Validate.notBlank(propertyName, "Property name required");
		return System.getProperty(propertyName);
	}

	/** {@inheritDoc} */
	public void setupActiviti(JavaPackage packageName) {
		log.info("ActivitiOperationsImpl.setupActiviti()");
		{
			/**
			 *  Parse the configuration.xml file
			 */
			log.info("Parsing the configuration.xml file...");
			final Element configuration = XmlUtils.getConfiguration(getClass());

			// Add POM properties
			updatePomProperties(configuration, projectOperations.getFocusedModuleName());

			// Add dependencies to POM
			updateDependencies(configuration, projectOperations.getFocusedModuleName());

			// Add activiti repository to POM
			updatePomRepositories(configuration, projectOperations.getFocusedModuleName());
			log.info("Parse the configuration.xml file DONE!");
		}

		{
			/**
			 *  Copy the applicationContext-activiti.xml template file
			 */
			log.info("Copying the applicationContext-activiti.xml template file...");

			String packagePath = packageName.getFullyQualifiedPackageName().replaceAll("\\.", "/");

			final String destinationFile = pathResolver.getFocusedIdentifier(Path.SPRING_CONFIG_ROOT,
					"applicationContext-activiti.xml");
			if (!fileManager.exists(destinationFile)) {
				InputStream templateInputStream = null;
				OutputStream outputStream = null;
				templateInputStream = FileUtils.getInputStream(getClass(), "applicationContext-activiti-template.xml");
				try {
					// Read template and insert the user's package
					String input = IOUtils.toString(templateInputStream);
					input = input.replace("__PACKAGE_NAME__", packagePath);

					// Output the file for the user
					final MutableFile mutableFile = fileManager.createFile(destinationFile);
					outputStream = mutableFile.getOutputStream();
					IOUtils.write(input, outputStream);
				} catch (final IOException ioe) {
					throw new IllegalStateException("Unable to create 'applicationContext-activiti.xml'", ioe);
				} finally {
					IOUtils.closeQuietly(templateInputStream);
					IOUtils.closeQuietly(outputStream);
				}
			} else {
				log.warning("The applicationContext-activiti.xml file already exists!");
			}
			log.info("Copied the applicationContext-activiti.xml template file!");
			log.warning("Please put your Activiti definition files in the \"" + packagePath + "\" folder!");
		}

	}

	public void setupActivitiExplorer() {
		log.info("ActivitiOperationsImpl.setupActivitiExplorer()");

		// TODO test jar not exists
		if (isActivitiExplorerInstalled()) {
			log.warning("Notice: install explorer again!");
		}

		{
			/**
			 *  update menu.jspx
			 */
			log.info("Updating the menu.jspx file...");
			final String menu_jspx = pathResolver.getFocusedIdentifier(Path.SRC_MAIN_WEBAPP, "WEB-INF" + SEPARATOR
					+ "views" + SEPARATOR + "menu.jspx");

			final Document document = XmlUtils.readXml(fileManager.getInputStream(menu_jspx));

			final Element menuRootElement = XmlUtils
					.findFirstElement("//*[@id='_menu']", document.getDocumentElement());

			final Document sourceDoc = XmlUtils.readXml(FileUtils.getInputStream(getClass(), "views" + "/"
					+ "menu_jspx.xml"));
			//  "//*[@id='_menu']"  "/menu" ok, "//menu" no
			final Element sourceElement = XmlUtils.findFirstElement("//*[@id='_menu']", sourceDoc.getDocumentElement());
			for (int i = 0; i < sourceElement.getChildNodes().getLength(); i++) {
				Element categroyElement = this.buildElement(document, sourceElement.getChildNodes().item(i), true);
				if (categroyElement != null) {
					menuRootElement.appendChild(categroyElement);
				}
			}

			fileManager.createOrUpdateTextFileIfRequired(menu_jspx, XmlUtils.nodeToString(document), false);

			log.info("The menu.jspx file updated!");
		}

		{
			/**
			 *  copy zero jar to WEB-INF/lib dictionary
			 */
			log.info("Copying the ibm-zero jar file to the WEB-INF/lib folder...");
			final String jarDirectory = pathResolver.getFocusedIdentifier(Path.SRC_MAIN_WEBAPP, "WEB-INF" + SEPARATOR
					+ "lib");
			copyDirectoryContents(IROO_CORE_LIBRARY, jarDirectory, false);
			log.info("The ibm-zero jar file copied!");
		}

		{
			/**
			 * copy views to WEB-INF/views directiory
			 */
			log.info("Copying views to WEB-INF/views directiory...");
			String targetViewsDirectory = pathResolver.getFocusedIdentifier(Path.SRC_MAIN_WEBAPP, "WEB-INF" + SEPARATOR
					+ "views");
			copyDirectoryContents("views/tasks/*.*", targetViewsDirectory + SEPARATOR + "tasks", false);
			copyDirectoryContents("views/process/*.*", targetViewsDirectory + SEPARATOR + "process", false);
			copyDirectoryContents("views/instance/*.*", targetViewsDirectory + SEPARATOR + "instance", false);

			log.info("Views copied!");
		}

		{
			/**
			 * copy i18n properties file
			 */
			log.info("Copying activiti's message properties file WEB-INF/i18n directiory...");
			copyDirectoryContents("i18n/activiti*.properties",
					pathResolver.getFocusedIdentifier(Path.SRC_MAIN_WEBAPP, "WEB-INF" + SEPARATOR + "i18n"), false);
			log.info("The message properties file copied!");
		}

		{
			/**	
			 *  <bean class="org.springframework.context.support.ReloadableResourceBundleMessageSource" id="messageSource" p:basenames="WEB-INF/i18n/messages,WEB-INF/i18n/activiti,WEB-INF/i18n/application" p:fallbackToSystemLocale="false"/>
			 */
			final String webmvcConfig_xml = pathResolver.getFocusedIdentifier(Path.SRC_MAIN_WEBAPP, "WEB-INF"
					+ SEPARATOR + "spring" + SEPARATOR + "webmvc-config.xml");
			final Document document = XmlUtils.readXml(fileManager.getInputStream(webmvcConfig_xml));

			final Element messageElement = XmlUtils.findFirstElement("/beans/bean[@id='messageSource']",
					document.getDocumentElement());

			String messageBeans = messageElement.getAttribute("p:basenames");
			messageElement.setAttribute("p:basenames", messageBeans + ",WEB-INF/i18n/activiti");

			fileManager.createOrUpdateTextFileIfRequired(webmvcConfig_xml, XmlUtils.nodeToString(document), false);

		}

		{
			/**
			 * ApplicationContext.xml , Webmvc-Config.xml
			 *     <context:component-scan base-package="com.ibm.zero.test2,com.ibm.zero" use-default-filters="false">
			<context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
			</context:component-scan>
			 */

			final String webmvcConfig_xml = pathResolver.getFocusedIdentifier(Path.SRC_MAIN_WEBAPP, "WEB-INF"
					+ SEPARATOR + "spring" + SEPARATOR + "webmvc-config.xml");
			final String applicationContext_xml = pathResolver.getFocusedIdentifier(Path.SPRING_CONFIG_ROOT,
					"applicationContext.xml");

			for (String xmlFile : new String[] { webmvcConfig_xml, applicationContext_xml }) {
				final Document document = XmlUtils.readXml(fileManager.getInputStream(xmlFile));

				final Element componentScanElement = XmlUtils.findFirstElement("/beans/component-scan",
						document.getDocumentElement());

				String componentScanPackages = componentScanElement.getAttribute("base-package");
				componentScanElement.setAttribute("base-package", componentScanPackages + "," + IROO_CORE_PACKAGE);//com.ibm.zero

				fileManager.createOrUpdateTextFileIfRequired(xmlFile, XmlUtils.nodeToString(document), false);
			}

		}

		// this.setupActivitiSamples();

	}

	public void setupActivitiSamples() {
		//		bean/property/deploymentResources value ="classpath*:/__PACKAGE_NAME__/*.bpmn20.xml" 

		final String activitiConfig = pathResolver.getFocusedIdentifier(Path.SPRING_CONFIG_ROOT,
				"applicationContext-activiti.xml");

		final Document document = XmlUtils.readXml(fileManager.getInputStream(activitiConfig));

		final Element deploymentProperty = XmlUtils.findFirstElement(
				"/beans/bean/property[@name='deploymentResources']", document.getDocumentElement());

		String deploymentPath = deploymentProperty.getAttribute("value");

		deploymentPath = deploymentPath.replace("classpath*:/", "");
		deploymentPath = deploymentPath.replace("/*.bpmn20.xml", "");

		if (!"/".equals(String.valueOf(SEPARATOR))) {
			deploymentPath = deploymentPath.replaceAll("/", "\\\\");
		}

		String targetProcessDirectory = pathResolver.getFocusedIdentifier(Path.SRC_MAIN_RESOURCES, deploymentPath);
		copyDirectoryContents("process/*.*", targetProcessDirectory, false);
	}

	private Element buildElement(Document document, Node sourceNode, boolean recurision) {
		//System.out.println("ActivitiOperationsImpl.buildElement()");
		if (sourceNode == null) {
			return null;
		}
		//		System.out.println("sourceNode.getNodeName()=" + sourceNode.getNodeName());

		if (sourceNode.getNodeName().startsWith("#text")) {
			return null;
		}
		XmlElementBuilder returnElement = new XmlElementBuilder(sourceNode.getNodeName(), document);

		if (sourceNode.getNodeName().startsWith("#text")) {
			String text = StringUtils.trim(sourceNode.getNodeName().substring(5));
			if (StringUtils.isEmpty(text)) {
				//		System.out.println("set text: " + text);
				returnElement.setText(text);
			}
		} else {

			if (sourceNode.getAttributes() != null) {
				NamedNodeMap attrs = sourceNode.getAttributes();
				for (int j = 0; j < attrs.getLength(); j++) {
					//		System.out.println("attr:" + attrs.item(j).getNodeName() + "=" + attrs.item(j).getNodeValue());
					returnElement.addAttribute(attrs.item(j).getNodeName(), attrs.item(j).getNodeValue());
				}
			}
			if (recurision) {
				NodeList nl = sourceNode.getChildNodes();
				if (nl != null) {
					for (int k = 0; k < nl.getLength(); k++) {
						Element child = this.buildElement(document, nl.item(k), recurision);
						if (child != null) {
							//		System.out.println("add child:" + child);
							returnElement.addChild(child);
						}
					}
				}
			}
		}
		return returnElement.build();
	}

	/**
	 * 
	 * @param configuration
	 * @param moduleName
	 */
	private void updateDependencies(final Element configuration, final String moduleName) {
		final List<Dependency> dependencies = new ArrayList<Dependency>();
		final List<Element> securityDependencies = XmlUtils.findElements(
				"/configuration/activiti/dependencies/dependency", configuration);
		for (final Element dependencyElement : securityDependencies) {
			dependencies.add(new Dependency(dependencyElement));
		}
		projectOperations.addDependencies(moduleName, dependencies);
	}

	private void updatePomProperties(final Element configuration, final String moduleName) {
		final List<Element> databaseProperties = XmlUtils.findElements("/configuration/activiti/properties/*",
				configuration);
		for (final Element property : databaseProperties) {
			projectOperations.addProperty(moduleName, new Property(property));
		}
	}

	private void updatePomRepositories(final Element configuration, final String moduleName) {
		final List<Element> databaseProperties = XmlUtils.findElements("/configuration/activiti/repositories/*",
				configuration);
		for (final Element repositoryElement : databaseProperties) {
			projectOperations.addRepository(moduleName, new Repository(repositoryElement));
		}
	}

}